#pragma once

#include <common.h>
#include <riscv/func.h>
#include <riscv/block.h>
#include <riscv/regs.h>
#include <riscv/global.h>
#include <riscv/float_number.h>

#define DEF_INST(INST, ...) \
  class INST : public Inst { \
  public: \
    __VA_ARGS__; \
    virtual ~INST() = default; \
    virtual std::string get_str() override; \
  }

namespace riscv {
  class Func;
  class Block;
}

namespace riscv {

  class Inst {
  public:
    virtual ~Inst();

    virtual std::string get_str() = 0;
  };

  DEF_INST(InstAddi, Reg rd; Reg src; int imm;);

  DEF_INST(InstAdd, Reg rd; Reg src1; Reg src2;);
  DEF_INST(InstSub, Reg rd; Reg src1; Reg src2;);
  DEF_INST(InstMul, Reg rd; Reg src1; Reg src2;);
  DEF_INST(InstDiv, Reg rd; Reg src1; Reg src2;);
  DEF_INST(InstAddw, Reg rd; Reg src1; Reg src2;);
  DEF_INST(InstSubw, Reg rd; Reg src1; Reg src2;);
  DEF_INST(InstMulw, Reg rd; Reg src1; Reg src2;);
  DEF_INST(InstDivw, Reg rd; Reg src1; Reg src2;);

  DEF_INST(InstSd, Reg rd; int off; Reg src;);
  DEF_INST(InstSw, Reg rd; int off; Reg src;);
  DEF_INST(InstLd, Reg rd; int off; Reg src;);
  DEF_INST(InstLw, Reg rd; int off; Reg src;);

  DEF_INST(InstJ, Block * block;);
  DEF_INST(InstBne, Reg src1; Reg src2; Block * block;);
  DEF_INST(InstBeq, Reg src1; Reg src2; Block * block;);

  DEF_INST(InstMv, Reg rd; Reg src;);
  DEF_INST(InstLi, Reg reg; int imm;);
  DEF_INST(InstCall, Func * func;);
  DEF_INST(InstRet);
  DEF_INST(
      InstLla,
      Reg rd;
      std::variant<Global*, FloatNumber*> global_float;
    );

  DEF_INST(InstSextW, Reg src; Reg rd;);

  DEF_INST(InstFaddD, FReg rd; FReg src1; FReg src2;);
  DEF_INST(InstFsubD, FReg rd; FReg src1; FReg src2;);
  DEF_INST(InstFmulD, FReg rd; FReg src1; FReg src2;);
  DEF_INST(InstFdivD, FReg rd; FReg src1; FReg src2;);

  DEF_INST(InstFsd, FReg rd; int off; Reg src;);
  DEF_INST(InstFsw, FReg rd; int off; Reg src;);
  DEF_INST(InstFld, FReg rd; int off; Reg src;);
  DEF_INST(InstFlw, FReg rd; int off; Reg src;);

  DEF_INST(InstFmvS, FReg rd; FReg src);
  DEF_INST(InstFmvD, FReg rd; FReg src);
  DEF_INST(InstFmvDX, FReg rd; Reg src);
  DEF_INST(InstFmvXD, Reg rd; FReg src);

  DEF_INST(InstFcvtDS, FReg rd; FReg src);
  DEF_INST(InstFcvtSD, FReg rd; FReg src);
  enum class FcvtRm { rtz };
  DEF_INST(InstFcvtLD, Reg rd; FReg src; FcvtRm rm);
  DEF_INST(InstFcvtDL, FReg rd; Reg src; FcvtRm rm);
}

#undef DEF_INST
